﻿using System;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;

namespace DestructibleTextureDemo
{
    /// <summary>
    /// Destructible Texture Class
    /// This class creates an object based on a XNA Texture2D object.
    /// The texture is destroyed using the method DestroyTexture.
    /// Destroy referes to turns an amount of pixels to transparent.
    /// </summary>
    public class DestructibleTexture2D
    {
        private readonly Texture2D _originalTexture;
        private readonly Rectangle _originalTextureSourceRectangle;

        public Texture2D Texture { get; private set; }
        public int Width { get { return Texture.Width; } }
        public int Height { get { return Texture.Height; } }

        public Color[] TextureData { get; private set; }

        /// <summary>
        /// Destructible Texture.
        /// </summary>
        /// <param name="texture">Original Texture.</param>
        /// <param name="graphicsDevice">Graphics Device from Game class.</param>
        public DestructibleTexture2D(Texture2D texture, GraphicsDevice graphicsDevice) : this(texture, null, graphicsDevice) { }

        /// <summary>
        /// Destrucitble Texture.
        /// </summary>
        /// <param name="texture">Original Texture.</param>
        /// <param name="sourceRectangle">Original Texture's source rectangle.</param>
        /// <param name="graphicsDevice">Graphics Device from Game class.</param>
        public DestructibleTexture2D(Texture2D texture, Rectangle? sourceRectangle, GraphicsDevice graphicsDevice)
        {
            _originalTexture = texture;
            _originalTextureSourceRectangle = sourceRectangle.HasValue ? sourceRectangle.Value : new Rectangle(0, 0, _originalTexture.Width, _originalTexture.Height);
            Texture = new Texture2D(graphicsDevice, _originalTextureSourceRectangle.Width, _originalTextureSourceRectangle.Height);

            Reset();
        }

        /// <summary>
        /// Destroys the texture.
        /// </summary>
        /// <param name="circle">Circle value.</param>
        public void DestroyTexture(Circle circle)
        {
            int areaLeft = Math.Max(0, (int)(circle.X - circle.Radius));
            int areaRight = Math.Min(Texture.Width, (int)(circle.X + circle.Radius));

            int areaTop = Math.Max(0, (int)(circle.Y - circle.Radius));
            int areaBottom = Math.Min(Texture.Height, (int)(circle.Y + circle.Radius));

            for (int i = areaLeft; i < areaRight; i++)
            {
                for (int j = areaTop; j < areaBottom; j++)
                {
                    if (circle.Contains(new Point(i, j)))
                    {
                        TextureData[i + j * Texture.Width] = Color.Transparent;
                    }
                }
            }
            Texture.SetData(TextureData);
        }


        /// <summary>
        /// Destroys the Texture.
        /// </summary>
        /// <param name="circle">Circle value.</param>
        /// <param name="textureChange">True if the texture value changed.</param>
        public void DestroyTexture(Circle circle, out bool textureChange)
        {
            textureChange = false;

            int areaLeft = Math.Max(0, (int)(circle.X - circle.Radius));
            int areaRight = Math.Min(Texture.Width, (int)(circle.X + circle.Radius));

            int areaTop = Math.Max(0, (int)(circle.Y - circle.Radius));
            int areaBottom = Math.Min(Texture.Height, (int)(circle.Y + circle.Radius));

            for (int i = areaLeft; i < areaRight; i++)
            {
                for (int j = areaTop; j < areaBottom; j++)
                {
                    if (circle.Contains(new Point(i, j)))
                    {
                        int dataIndex = i + j * Texture.Width;
                        if (TextureData[dataIndex].A != 0)
                        {
                            TextureData[i + j * Texture.Width] = Color.Transparent;
                            textureChange = true;
                        }
                    }
                }
            }

            if (textureChange)
                Texture.SetData(TextureData);
        }

        /// <summary>
        /// Destroys texture.
        /// </summary>
        /// <param name="rectangle">Rectangle value.</param>
        public void DestroyTexture(Rectangle rectangle)
        {
            int areaLeft = Math.Max(0, rectangle.Left);
            int areaRight = Math.Min(Texture.Width, rectangle.Right);

            int areaTop = Math.Max(0, rectangle.Top);
            int areaBottom = Math.Min(Texture.Height, rectangle.Bottom);

            for (int i = areaLeft; i < areaRight; i++)
            {
                for (int j = areaTop; j < areaBottom; j++)
                {
                    TextureData[i + j * Texture.Width] = Color.Transparent;
                }
            }
            Texture.SetData(TextureData);
        }

        /// <summary>
        /// Destroys texture.
        /// </summary>
        /// <param name="rectangle">Rectangle Value.</param>
        /// <param name="textureChange">True if texture value changed.</param>
        public void DestroyTexture(Rectangle rectangle, out bool textureChange)
        {
            textureChange = false;
            int areaLeft = Math.Max(0, rectangle.Left);
            int areaRight = Math.Min(Texture.Width, rectangle.Right);

            int areaTop = Math.Max(0, rectangle.Top);
            int areaBottom = Math.Min(Texture.Height, rectangle.Bottom);

            for (int i = areaLeft; i < areaRight; i++)
            {
                for (int j = areaTop; j < areaBottom; j++)
                {
                    TextureData[i + j * Texture.Width] = Color.Transparent;
                    textureChange = true;
                }
            }

            if (textureChange)
                Texture.SetData(TextureData);
        }

        /// <summary>
        /// Destroys texture.
        /// </summary>
        /// <param name="position">Position where to apply destruction.</param>
        /// <param name="texture">Texture used to destroy.</param>
        /// <param name="textureSourceRectangle">Source rectangle of that texture, use null if full texture is used.</param>
        public void DestroyTexture(Vector2 position, Texture2D texture, Rectangle? textureSourceRectangle)
        {
            Rectangle rectangle;
            if (textureSourceRectangle.HasValue)
            {
                rectangle = new Rectangle((int)position.X, (int)position.Y, textureSourceRectangle.Value.Width,
                                          textureSourceRectangle.Value.Height);
            }
            else
            {
                rectangle = new Rectangle((int)position.X, (int)position.Y, texture.Width, texture.Height);
            }

            Color[] intersectionTextureData = new Color[rectangle.Width * rectangle.Height];
            texture.GetData(0, textureSourceRectangle, intersectionTextureData, 0, intersectionTextureData.Length);

            int areaLeft = Math.Max(0, rectangle.Left);
            int areaRight = Math.Min(Texture.Width, rectangle.Right);

            int areaTop = Math.Max(0, rectangle.Top);
            int areaBottom = Math.Min(Texture.Height, rectangle.Bottom);

            for (int i = areaLeft; i < areaRight; i++)
            {
                for (int j = areaTop; j < areaBottom; j++)
                {
                    if (intersectionTextureData[(i - areaLeft) + (j - areaTop) * rectangle.Width].A != 0)
                        TextureData[i + j * Texture.Width] = Color.Transparent;
                }
            }
            Texture.SetData(TextureData);
        }

        /// <summary>
        /// Destroys texture.
        /// </summary>
        /// <param name="position">Position where to apply destruction.</param>
        /// <param name="texture">Texture used to destroy.</param>
        /// <param name="textureSourceRectangle">Source rectangle of that texture, use null if full texture is used.</param>
        /// <param name="textureChanged">True is texture value changed.</param>
        public void DestroyTexture(Vector2 position, Texture2D texture, Rectangle? textureSourceRectangle, out bool textureChanged)
        {
            textureChanged = false;

            Rectangle rectangle;
            if (textureSourceRectangle.HasValue)
            {
                rectangle = new Rectangle((int)position.X, (int)position.Y, textureSourceRectangle.Value.Width,
                                          textureSourceRectangle.Value.Height);
            }
            else
            {
                rectangle = new Rectangle((int)position.X, (int)position.Y, texture.Width, texture.Height);
            }

            Color[] intersectionTextureData = new Color[rectangle.Width * rectangle.Height];
            texture.GetData(0, textureSourceRectangle, intersectionTextureData, 0, intersectionTextureData.Length);

            int areaLeft = Math.Max(0, rectangle.Left);
            int areaRight = Math.Min(Texture.Width, rectangle.Right);

            int areaTop = Math.Max(0, rectangle.Top);
            int areaBottom = Math.Min(Texture.Height, rectangle.Bottom);

            for (int i = areaLeft; i < areaRight; i++)
            {
                for (int j = areaTop; j < areaBottom; j++)
                {
                    if (intersectionTextureData[(i - areaLeft) + (j - areaTop) * rectangle.Width].A != 0)
                    {
                        if (TextureData[i + j * Texture.Width].A != 0)
                        {
                            TextureData[i + j * Texture.Width] = Color.Transparent;
                            textureChanged = true;
                        }
                    }
                }
            }

            if (textureChanged)
                Texture.SetData(TextureData);
        }


        /// <summary>
        /// Resets the destructible texture to its orignal state.
        /// </summary>
        public void Reset()
        {
            TextureData = new Color[_originalTextureSourceRectangle.Width * _originalTextureSourceRectangle.Height];
            _originalTexture.GetData(0, _originalTextureSourceRectangle, TextureData, 0, TextureData.Length);

            Texture.SetData(TextureData);
        }

        /// <summary>
        /// Used for simplification, that way we don't have to always be calling destructiveTexture.Texture
        /// </summary>
        public static implicit operator Texture2D(DestructibleTexture2D text)
        {
            return text.Texture;
        }
    }
}
